#pragma once

#include "oj_view.hpp"
#include <atomic>
#include "../common/httplib.h"

/* ********************************************************************************
 * 打算后期做一个负载均衡器
 * 另外，解决单点暴毙问题
 * 现在对于HostDesc负载，根据记录请求来计算，所以多机器访问是，对load++是并发的，加锁
 * 但是std::mutex仅用了拷贝构造，这里选择对HostDesc使用智能指针来解决这个问题
 ********************************************************************************** */
class HostDesc
{
public:
    using ptr = std::shared_ptr<HostDesc>;

    std::string _ip;
    uint16_t _port;
    std::atomic<int> _load;
    std::mutex _mtx;

public:
    HostDesc(const std::string &ip, uint16_t port, int load = 0) : _ip(ip), _port(port), _load(0) // 这里写错了
    {
    }
    void Modify(int load)
    {
        _load.store(load);
    }
    // 根据记录请求来计算load
    void UpLoad()
    {
        ++_load;
    }
    void DownLoad()
    {
        --_load;
    }
    void ResetLoad()
    {
        _load.store(0);
    }
    ~HostDesc() = default;
};

/* *************************
 * 后期选择使用rpc进行通信
 * 先使用简单的Host管理
 * *************************/

const std::string service_machine = "./conf/service_machine.conf";

class HostModule
{
private:
    std::vector<HostDesc::ptr> machines;
    std::vector<int> online;
    std::vector<int> offline;
    std::mutex mtx;

public:
    void PrintNowStatus()
    {
        DLOG(DEBUG) << "打印当前主机情况";
        std::stringstream ss;
        for (auto e : online)
        {
            ss << e << ' ';
        }
        DLOG(DEBUG) << ss.str();
        std::stringstream ss1;
        for (auto e : offline)
        {
            ss1 << e << ' ';
        }
        DLOG(DEBUG) << ss1.str();
    }
    HostModule()
    {
        assert(LoadConf(service_machine));
        DLOG(INFO) << "加载 " << service_machine << " 成功";
    }
    ~HostModule()
    {
    }

public:
    // 通过读取配置文件加载服务器的信息
    bool LoadConf(const std::string &machine_conf)
    {
        std::ifstream in(machine_conf);
        if (!in.is_open())
        {
            DLOG(FATAL) << " 加载: " << machine_conf << " 失败";
            return false;
        }
        std::string line;
        while (std::getline(in, line))
        {
            std::vector<std::string> tokens;
            StringUtil::SplitString(line, tokens, ":");
            // 这里成功了没，打印以下
            std::stringstream ss;
            for (auto &e : tokens)
            {
                ss << e << ' ';
            }
            DLOG(DEBUG) << ss.str();

            if (tokens.size() != 2)
            {
                DLOG(WARNING) << " 切分 " << line << " 失败";
                continue;
            }
            HostDesc::ptr m = std::make_shared<HostDesc>(tokens[0], atoi(tokens[1].c_str()), 0);

            online.push_back(machines.size());
            machines.push_back(m);
        }

        in.close();
        return true;
    }

    // 负载均衡选择主机
    bool SmartChoice(int &id, HostDesc::ptr &m)
    {
        mtx.lock();

        int online_num = online.size();
        if (online_num == 0)
        {
            mtx.unlock();
            DLOG(FATAL) << " 所有的后端编译主机已经离线";
            return false;
        }
        // 通过遍历的方式，找到所有负载最小的机器
        // TODO 使用优先级队列优化
        id = online[0];
        m = machines[online[0]];
        uint64_t min_load = machines[online[0]]->_load;
        for (int i = 1; i < online_num; i++)
        {
            uint64_t curr_load = machines[online[i]]->_load;
            if (min_load > curr_load)
            {
                min_load = curr_load;
                id = online[i];
                m = machines[online[i]];
            }
        }
        m->UpLoad();
        DLOG(INFO) << " 选择主机成功, 主机id: " << id << " 详情: " << m->_ip << ":" << m->_port << " 当前主机的负载是: " << m->_load;
        mtx.unlock();
        return true;
    }
    void OfflineMachine(int which)
    {
        mtx.lock();
        for (auto iter = online.begin(); iter != online.end(); iter++)
        {
            if (*iter == which)
            {
                machines[which]->ResetLoad();
                // 要离线的主机已经找到啦
                DLOG(ERROR) << "主机: " << std::to_string(which) << "离线了" << "ip: " << machines[which]->_ip << "port: " << std::to_string(machines[which]->_port);
                online.erase(iter);
                offline.push_back(which);
                break; // 因为break的存在，不考虑迭代器失效的问题
            }
        }
        PrintNowStatus();
        mtx.unlock();
    }
    void OnlineMachine()
    {
        // 统一上线，后面统一解决
        mtx.lock();
        online.insert(online.end(), offline.begin(), offline.end());
        offline.erase(offline.begin(), offline.end());
        mtx.unlock();

        DLOG(INFO) << "所有的主机上线啦!";
    }
    // for test
    void ShowMachines()
    {
        mtx.lock();
        std::cout << "当前在线主机列表: ";
        for (auto &id : online)
        {
            std::cout << id << " ";
        }
        std::cout << std::endl;
        std::cout << "当前离线主机列表: ";
        for (auto &id : offline)
        {
            std::cout << id << " ";
        }
        std::cout << std::endl;
        mtx.unlock();
    }
};

// 控制器
class Control
{
private:
    QuestionsModule _questions; // 提供后台数据
    View _view;                 // 提供html渲染功能
    HostModule _host_module;    // 核心负载均衡器
public:
    Control()
    {
    }
    ~Control()
    {
    }

public:
    void RecoveryMachine()
    {
        _host_module.OnlineMachine();
    }
    bool AllQuestions(std::string &html)
    {
        bool ret = true;
        std::vector<QuestionDescribe::ptr> all;
        if (_questions.GetAllQuestions(all))
        {
            sort(all.begin(), all.end(), [](const QuestionDescribe::ptr &q1, const QuestionDescribe::ptr &q2)
                 { return std::stoi(q1->id) < std::stoi(q2->id); });
            // 获取所有的表述信息，交给View进行html渲染
            _view.AllExpandHtml(all, html);
        }
        else
        {
            html = "获取题目列表失败";
            ret = false;
        }
        return ret;
    }
    bool GetOneQuestion(const std::string &id, std::string &html)
    {
        bool ret = true;
        QuestionDescribe::ptr q;
        if (_questions.GetOneQuestion(id, q))
        {
            _view.OneExpandHtml(q, html);
        }
        else
        {
            html = "题目: " + id + "不存在";
            ret = false;
        }
        return ret;
    }
    // 首先根据id找到对应的题目信息，然后对收到的消息进行处理、拼凑，然后输出到out_json里，然后向指定的host发送Post请求，并对相应进行处理
    // 这个模块完全可以使用基于muduo的Rpc的回调函数进行优化
    // TODO
    // 完成接收客户端Json::Value，处理、运行，然后把标准错误标准输出输出到string
    /* ******************************
     * 客户端发送Json格式：
     * code:
     * stdin:
     * cpu_limit:
     * mem_limit:
     * 服务端输出Json格式：
     * desc:
     * status:
     * stdout:
     * stderr:
     ******************************* */
    void ToHost(const std::string &number, const std::string &in_json, std::string &out_json)
    {
        DLOG(DEBUG) << in_json;
        QuestionDescribe::ptr q;
        if (_questions.GetOneQuestion(number, q))
        {
            Json::Value in_value;

            // 现在in_json只有用户端的代码内容，需要进行拼接
            util_ns::JSON::unserializer(in_json, in_value);
            const std::string &code = in_value["code"].asString();

            Json::Value out_value;
            out_value["stdin"] = in_value["stdin"].asString();
            out_value["code"] = code + '\n' + q->tail;
            out_value["cpu_limit"] = q->cpu_load;
            out_value["mem_limit"] = q->mem_load;

            DLOG(DEBUG) << "完成out_value的填充";
            std::string toHostStr;
            DLOG(DEBUG) << "完成out_value的序列化";
            util_ns::JSON::serializer(toHostStr, out_value);

            // 完成处理工作，开始寻找客户端发送数据
            int id = -1;
            HostDesc::ptr machine;
            while (true)
            {
                if (_host_module.SmartChoice(id, machine))
                {
                    DLOG(DEBUG) << "根据ip:port实例化对应客户端对象" << machine->_ip << ':' << machine->_port;
                    httplib::Client cli(machine->_ip, machine->_port);
                    // 开始发送请求,接收到的ret是一个类
                    DLOG(DEBUG) << "开始向指定机器发送请求";
                    // compile_and_run
                    auto ret = cli.Post("/compile_and_run", toHostStr, /*表示请求字段是json类型，是utf-8编码*/ "application/json;charset=utf-8");
                    DLOG(DEBUG) << "发送请求成功，对返回值进行判断";
                    // ret 是空的。
                    // DLOG(DEBUG) << "返回值是：" << ret->status;

                    if (ret == nullptr)
                    {
                        // 说明对应的服务器挂掉了
                        _host_module.OfflineMachine(id);
                        continue;
                    }
                    else if (ret->status == 200)
                    {
                        out_json = ret->body;
                        machine->DownLoad();
                        DLOG(INFO) << "请求编译和运行服务成功..." << "\n";
                        break;
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    // 说明所有的主机都离线了，需要重启所有主机
                    // TODO
                    DLOG(FATAL) << "运维快上线，所有主机都G了";
                    break;
                }
            }
        }
        else
        {
            // 获取题目失败，不做处理
        }
    }
};